/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.transform.wavelet.impl;

import boofcv.alg.transform.wavelet.UtilWavelet;
import boofcv.core.image.border.BorderIndex1D;
import boofcv.struct.image.ImageFloat32;
import boofcv.struct.image.ImageInteger;
import boofcv.struct.wavelet.WlBorderCoef;
import boofcv.struct.wavelet.WlCoef_F32;
import boofcv.struct.wavelet.WlCoef_I32;


/**
 * <p>
 * Unoptimized and simplistic implementation of a forward and inverse wavelet transform across one
 * level.  Primary used for validation testing.
 * </p>
 *
 * <p>
 * DO NOT MODIFY: This class was automatically generated by {@link GenerateImplWaveletTransformNaive}.
 * </p>
 *
 * @author Peter Abeles
 */
public class ImplWaveletTransformNaive {

	/**
	 * Performs a single level wavelet transform along the horizontal axis.
	 *
	 * @param coefficients Description of wavelet coefficients.
	 * @param input Input image which is being transform. Not modified.
	 * @param output where the output is written to. Modified
	 */
	public static void horizontal( BorderIndex1D border , WlCoef_F32 coefficients ,
								   ImageFloat32 input , ImageFloat32 output ) {

		UtilWavelet.checkShape(input,output);

		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final float[] alpha = coefficients.scaling;
		final float[] beta = coefficients.wavelet;

		border.setLength(input.width+input.width%2);

		final boolean isLarger = output.width > input.width;

		for( int y = 0; y < input.height; y++ ) {
			for( int x = 0; x < input.width; x += 2 ) {

				float scale = 0;
				float wavelet = 0;

				for( int i = 0; i < alpha.length; i++ ) {
					int xx = border.getIndex(x+i+offsetA);
					if( isLarger && xx >= input.width )
						continue;
					scale += input.get(xx,y)*alpha[i];
				}
				for( int i = 0; i < beta.length; i++ ) {
					int xx = border.getIndex(x+i+offsetB);
					if( isLarger && xx >= input.width )
						continue;
					wavelet += input.get(xx,y)*beta[i];
				}

				int outX = x/2;

				output.set(outX,y,scale);
				output.set(output.width/2 + outX , y , wavelet );
			}
		}
	}

	/**
	 * Performs a single level wavelet transform along the vertical axis.
	 *
	 * @param coefficients Description of wavelet coefficients.
	 * @param input Input image which is being transform. Not modified.
	 * @param output where the output is written to. Modified
	 */
	public static void vertical( BorderIndex1D border , WlCoef_F32 coefficients ,
								 ImageFloat32 input , ImageFloat32 output ) {

		UtilWavelet.checkShape(input,output);

		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final float[] alpha = coefficients.scaling;
		final float[] beta = coefficients.wavelet;

		border.setLength(input.height+input.height%2);

		boolean isLarger = output.height > input.height;

		for( int x = 0; x < input.width; x++) {
			for( int y = 0; y < input.height; y += 2 ) {
				float scale = 0;
				float wavelet = 0;

				for( int i = 0; i < alpha.length; i++ ) {
					int yy = border.getIndex(y+i+offsetA);
					if( isLarger && yy >= input.height )
						continue;
					scale += input.get(x,yy)*alpha[i];
				}
				for( int i = 0; i < beta.length; i++ ) {
					int yy = border.getIndex(y+i+offsetB);
					if( isLarger && yy >= input.height )
						continue;
					wavelet += input.get(x,yy)*beta[i];
				}

				int outY = y/2;

				output.set(x , outY,scale);
				output.set(x , output.height/2 + outY , wavelet );
			}
		}
	}

	/**
	 * Performs a single level inverse wavelet transform along the horizontal axis.
	 *
	 * @param inverseCoef Description of wavelet coefficients.
	 * @param input Transformed image. Not modified.
	 * @param output Reconstruction of original image. Modified
	 */
	public static void horizontalInverse( BorderIndex1D border , WlBorderCoef<WlCoef_F32> inverseCoef , ImageFloat32 input , ImageFloat32 output ) {

		UtilWavelet.checkShape(output,input);

		float []trends = new float[ output.width ];
		float []details = new float[ output.width ];

		boolean isLarger = input.width >= output.width;
		int paddedWidth = output.width + output.width%2;

		final int lowerBorder = inverseCoef.getLowerLength()*2;
		final int upperBorder = output.width - inverseCoef.getUpperLength()*2;

		border.setLength(output.width+output.width%2);

		WlCoef_F32 coefficients;

		for( int y = 0; y < output.height; y++ ) {

			for( int i = 0; i < details.length; i++ ) {
				details[i] = 0;
				trends[i] = 0;
			}

			for( int x = 0; x < output.width; x += 2 ) {
				float a = input.get(x/2,y);
				float d = input.get(input.width/2+x/2,y);

				if( x < lowerBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(x);
				} else if( x >= upperBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(x-paddedWidth);
				} else {
					coefficients = inverseCoef.getInnerCoefficients();
				}

				final int offsetA = coefficients.offsetScaling;
				final int offsetB = coefficients.offsetWavelet;
				final float[] alpha = coefficients.scaling;
				final float[] beta = coefficients.wavelet;

				// add the trend
				for( int i = 0; i < alpha.length; i++ ) {
					// if an odd image don't update the outer edge
					int xx = border.getIndex(x+offsetA+i);
					if( isLarger && xx >= output.width )
						continue;
					trends[xx] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 0; i < beta.length; i++ ) {
					int xx = border.getIndex(x+offsetB+i);
					if( isLarger && xx >= output.width )
						continue;
					details[xx] += d*beta[i];
				}
			}

			for( int x = 0; x < output.width; x++ ) {
				output.set(x,y, trends[x] + details[x]);
			}
		}
	}

	/**
	 * Performs a single level inverse wavelet transform along the vertical axis.
	 *
	 * @param inverseCoef Description of wavelet coefficients.
	 * @param input Transformed image. Not modified.
	 * @param output Reconstruction of original image. Modified
	 */
	public static void verticalInverse( BorderIndex1D border , WlBorderCoef<WlCoef_F32> inverseCoef , ImageFloat32 input , ImageFloat32 output ) {

		UtilWavelet.checkShape(output,input);

		float []trends = new float[ output.height ];
		float []details = new float[ output.height ];

		boolean isLarger = input.height > output.height;
		int paddedHeight = output.height + output.height%2;

		final int lowerBorder = inverseCoef.getLowerLength()*2;
		final int upperBorder = output.height - inverseCoef.getUpperLength()*2;

		border.setLength(output.height+output.height%2);

		WlCoef_F32 coefficients;

		for( int x = 0; x < output.width; x++) {

			for( int i = 0; i < details.length; i++ ) {
				details[i] = 0;
				trends[i] = 0;
			}

			for( int y = 0; y < output.height; y += 2 ) {
				float a = input.get(x,y/2);
				float d = input.get(x,y/2+input.height/2);

				if( y < lowerBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(y);
				} else if( y >= upperBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(y-paddedHeight);
				} else {
					coefficients = inverseCoef.getInnerCoefficients();
				}

				final int offsetA = coefficients.offsetScaling;
				final int offsetB = coefficients.offsetWavelet;
				final float[] alpha = coefficients.scaling;
				final float[] beta = coefficients.wavelet;

				// add the 'average' signal
				for( int i = 0; i < alpha.length; i++ ) {
					// if an odd image don't update the outer edge
					int yy = border.getIndex(y+offsetA+i);
					if( isLarger && yy >= output.height )
						continue;
					trends[yy] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 0; i < beta.length; i++ ) {
					int yy = border.getIndex(y+offsetB+i);
					if( isLarger && yy >= output.height )
						continue;
					details[yy] += d*beta[i];
				}
			}

			for( int y = 0; y < output.height; y++ ) {
				output.set(x,y, trends[y] + details[y]);
			}
		}
	}

	/**
	 * Performs a single level wavelet transform along the horizontal axis.
	 *
	 * @param coefficients Description of wavelet coefficients.
	 * @param input Input image which is being transform. Not modified.
	 * @param output where the output is written to. Modified
	 */
	public static void horizontal( BorderIndex1D border , WlCoef_I32 coefficients ,
								   ImageInteger input , ImageInteger output ) {

		UtilWavelet.checkShape(input,output);

		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final int[] alpha = coefficients.scaling;
		final int[] beta = coefficients.wavelet;

		border.setLength(input.width+input.width%2);

		final boolean isLarger = output.width > input.width;

		for( int y = 0; y < input.height; y++ ) {
			for( int x = 0; x < input.width; x += 2 ) {

				int scale = 0;
				int wavelet = 0;

				for( int i = 0; i < alpha.length; i++ ) {
					int xx = border.getIndex(x+i+offsetA);
					if( isLarger && xx >= input.width )
						continue;
					scale += input.get(xx,y)*alpha[i];
				}
				for( int i = 0; i < beta.length; i++ ) {
					int xx = border.getIndex(x+i+offsetB);
					if( isLarger && xx >= input.width )
						continue;
					wavelet += input.get(xx,y)*beta[i];
				}

				int outX = x/2;

				scale = 2*scale/coefficients.denominatorScaling;
				wavelet = 2*wavelet/coefficients.denominatorWavelet;

				output.set(outX,y,scale);
				output.set(output.width/2 + outX , y , wavelet );
			}
		}
	}

	/**
	 * Performs a single level wavelet transform along the vertical axis.
	 *
	 * @param coefficients Description of wavelet coefficients.
	 * @param input Input image which is being transform. Not modified.
	 * @param output where the output is written to. Modified
	 */
	public static void vertical( BorderIndex1D border , WlCoef_I32 coefficients ,
								 ImageInteger input , ImageInteger output ) {

		UtilWavelet.checkShape(input,output);

		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final int[] alpha = coefficients.scaling;
		final int[] beta = coefficients.wavelet;

		border.setLength(input.height+input.height%2);

		boolean isLarger = output.height > input.height;

		for( int x = 0; x < input.width; x++) {
			for( int y = 0; y < input.height; y += 2 ) {
				int scale = 0;
				int wavelet = 0;

				for( int i = 0; i < alpha.length; i++ ) {
					int yy = border.getIndex(y+i+offsetA);
					if( isLarger && yy >= input.height )
						continue;
					scale += input.get(x,yy)*alpha[i];
				}
				for( int i = 0; i < beta.length; i++ ) {
					int yy = border.getIndex(y+i+offsetB);
					if( isLarger && yy >= input.height )
						continue;
					wavelet += input.get(x,yy)*beta[i];
				}

				int outY = y/2;

				scale = 2*scale/coefficients.denominatorScaling;
				wavelet = 2*wavelet/coefficients.denominatorWavelet;

				output.set(x , outY,scale);
				output.set(x , output.height/2 + outY , wavelet );
			}
		}
	}

	/**
	 * Performs a single level inverse wavelet transform along the horizontal axis.
	 *
	 * @param inverseCoef Description of wavelet coefficients.
	 * @param input Transformed image. Not modified.
	 * @param output Reconstruction of original image. Modified
	 */
	public static void horizontalInverse( BorderIndex1D border , WlBorderCoef<WlCoef_I32> inverseCoef , ImageInteger input , ImageInteger output ) {

		UtilWavelet.checkShape(output,input);

		int []trends = new int[ output.width ];
		int []details = new int[ output.width ];

		boolean isLarger = input.width >= output.width;
		int paddedWidth = output.width + output.width%2;

		final int lowerBorder = inverseCoef.getLowerLength()*2;
		final int upperBorder = output.width - inverseCoef.getUpperLength()*2;

		border.setLength(output.width+output.width%2);

		WlCoef_I32 coefficients = inverseCoef.getInnerCoefficients();
		final int e = coefficients.denominatorScaling*2;
		final int f = coefficients.denominatorWavelet*2;
		final int ef = e*f;
		final int ef2 = ef/2;

		for( int y = 0; y < output.height; y++ ) {

			for( int i = 0; i < details.length; i++ ) {
				details[i] = 0;
				trends[i] = 0;
			}

			for( int x = 0; x < output.width; x += 2 ) {
				int a = input.get(x/2,y);
				int d = input.get(input.width/2+x/2,y);

				if( x < lowerBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(x);
				} else if( x >= upperBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(x-paddedWidth);
				} else {
					coefficients = inverseCoef.getInnerCoefficients();
				}

				final int offsetA = coefficients.offsetScaling;
				final int offsetB = coefficients.offsetWavelet;
				final int[] alpha = coefficients.scaling;
				final int[] beta = coefficients.wavelet;

				// add the trend
				for( int i = 0; i < alpha.length; i++ ) {
					// if an odd image don't update the outer edge
					int xx = border.getIndex(x+offsetA+i);
					if( isLarger && xx >= output.width )
						continue;
					trends[xx] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 0; i < beta.length; i++ ) {
					int xx = border.getIndex(x+offsetB+i);
					if( isLarger && xx >= output.width )
						continue;
					details[xx] += d*beta[i];
				}
			}

			for( int x = 0; x < output.width; x++ ) {
				output.set(x,y, UtilWavelet.round(trends[x]*f + details[x]*e ,ef2,ef));
			}
		}
	}

	/**
	 * Performs a single level inverse wavelet transform along the vertical axis.
	 *
	 * @param inverseCoef Description of wavelet coefficients.
	 * @param input Transformed image. Not modified.
	 * @param output Reconstruction of original image. Modified
	 */
	public static void verticalInverse( BorderIndex1D border , WlBorderCoef<WlCoef_I32> inverseCoef , ImageInteger input , ImageInteger output ) {

		UtilWavelet.checkShape(output,input);

		int []trends = new int[ output.height ];
		int []details = new int[ output.height ];

		boolean isLarger = input.height > output.height;
		int paddedHeight = output.height + output.height%2;

		final int lowerBorder = inverseCoef.getLowerLength()*2;
		final int upperBorder = output.height - inverseCoef.getUpperLength()*2;

		border.setLength(output.height+output.height%2);

		WlCoef_I32 coefficients = inverseCoef.getInnerCoefficients();
		final int e = coefficients.denominatorScaling*2;
		final int f = coefficients.denominatorWavelet*2;
		final int ef = e*f;
		final int ef2 = ef/2;

		for( int x = 0; x < output.width; x++) {

			for( int i = 0; i < details.length; i++ ) {
				details[i] = 0;
				trends[i] = 0;
			}

			for( int y = 0; y < output.height; y += 2 ) {
				int a = input.get(x,y/2);
				int d = input.get(x,y/2+input.height/2);

				if( y < lowerBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(y);
				} else if( y >= upperBorder ) {
					coefficients = inverseCoef.getBorderCoefficients(y-paddedHeight);
				} else {
					coefficients = inverseCoef.getInnerCoefficients();
				}

				final int offsetA = coefficients.offsetScaling;
				final int offsetB = coefficients.offsetWavelet;
				final int[] alpha = coefficients.scaling;
				final int[] beta = coefficients.wavelet;

				// add the 'average' signal
				for( int i = 0; i < alpha.length; i++ ) {
					// if an odd image don't update the outer edge
					int yy = border.getIndex(y+offsetA+i);
					if( isLarger && yy >= output.height )
						continue;
					trends[yy] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 0; i < beta.length; i++ ) {
					int yy = border.getIndex(y+offsetB+i);
					if( isLarger && yy >= output.height )
						continue;
					details[yy] += d*beta[i];
				}
			}

			for( int y = 0; y < output.height; y++ ) {
				output.set(x,y, UtilWavelet.round(trends[y]*f + details[y]*e,ef2,ef));
			}
		}
	}


}
